﻿using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Services;
using umbraco.presentation.umbraco.controls;
using umbraco.cms.presentation.Trees;
using System.Web.Script.Services;
using System.Web.Script.Serialization;
using System.IO;
using System.Web.UI;
using umbraco.controls.Tree;
using umbraco;
using umbraco.cms.businesslogic;
using System.Linq;
using System.Xml.XPath;
using System.Xml.Linq;
using umbraco.IO;
using umbraco.cms.businesslogic.media;
using umbraco.DataLayer;
using umbraco.BusinessLogic;
using umbraco.interfaces;
using umbraco.cms.businesslogic.datatype.controls;

namespace DigibizTree.Shared.Tree
{
    /// <summary>
    /// Client side ajax utlities for the tree
    /// </summary>
    [ScriptService]
    [WebService]
    public class DigibizService : WebService
    {
        protected static ISqlHelper SqlHelper
        {
            get
            {
                return umbraco.BusinessLogic.Application.SqlHelper;
            }
        }

        /// <summary>
        /// Returns a key/value object with: json, app, js as the keys
        /// </summary>	
        /// <returns></returns>
        [WebMethod]
        [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
        public Dictionary<string, string> GetInitDigibizTreeData(string app, string treeType, bool showContextMenu, bool isDialog, TreeDialogModes dialogMode, string functionToCall, string nodeKey)
        {
            Authorize();

            TreeControl treeCtl = new TreeControl()
            {
                ShowContextMenu = showContextMenu,
                IsDialog = isDialog,
                DialogMode = dialogMode,
                App = app,
                TreeType = string.IsNullOrEmpty(treeType) ? "" : treeType, //don't set the tree type unless explicitly set
                NodeKey = string.IsNullOrEmpty(nodeKey) ? "" : nodeKey,
                //StartNodeID = -1, //TODO: set this based on parameters!
                FunctionToCall = string.IsNullOrEmpty(functionToCall) ? "" : functionToCall
            };

            Dictionary<string, string> returnVal = new Dictionary<string, string>();


            if (string.IsNullOrEmpty(treeType))
            {
                //If there's not tree type specified, then render out the tree as per normal with the normal 
                //way of doing things.
                returnVal.Add("json", treeCtl.GetJSONInitNode());
            }
            else
            {
                //Since 4.5.1 has a bug in it, it ignores if the treeType is specified and will always only render the whole APP not just a specific tree. 
                //This is a work around for this bug until it is fixed (which should be fixed in 4.5.20).

                //Get the tree that we need to render.
                var tree = TreeDefinitionCollection.Instance.FindTree(treeType).CreateInstance();
                tree.ShowContextMenu = showContextMenu;
                tree.IsDialog = isDialog;
                tree.DialogMode = dialogMode;
                tree.NodeKey = string.IsNullOrEmpty(nodeKey) ? "" : nodeKey;
                tree.FunctionToCall = string.IsNullOrEmpty(functionToCall) ? "" : functionToCall;

                //Now render it's start node.
                XmlTree xTree = new XmlTree();
                
                var node = tree.RootNode;
                xTree.Add(node);

                returnVal.Add("json", xTree.ToString());
            }
            
            returnVal.Add("app", app);
            returnVal.Add("js", treeCtl.JSCurrApp);

            return returnVal;
        }

        internal static void Authorize()
        {
            if (!umbraco.BasePages.BasePage.ValidateUserContextID(umbraco.BasePages.BasePage.umbracoUserContextID))
            {
                throw new Exception("Client authorization failed. User is not logged in");
            }

        }

        /// <summary>
        /// Returns the media item as json for selecting in the popup.
        /// </summary>
        /// <param name="nodeId"></param>
        /// <returns></returns>
        [WebMethod]
        [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
        public Dictionary<string, object> GetPickerData(int nodeId, string allowedExtensions, string allowedMediaTypes, string cropPropertyAlias, string cropName)
        {
            Dictionary<string, object> rVal = new Dictionary<string, object>();

            if (nodeId == DigibizConstants.NOACCESSID)
            {
                //If the nodeId is the no acces id return no data.
                return rVal;
            }

            //Get the media item from the umbraco cache.
            XPathNodeIterator mediaIterator = library.GetMedia(nodeId, false);

            //Convert the XPathNodeIterator to an XDocument.
            XDocument xmlDocument = XDocument.Parse(mediaIterator.Current.OuterXml);

            //Get the root element.
            XElement mediaElement = xmlDocument.Root;

            allowedExtensions = allowedExtensions.Replace(" ", string.Empty).ToLower();

            rVal.Add("mediaItems", GetAllMediaItems(DigibizMediaHelper.GetMediaId(mediaElement), allowedExtensions, allowedMediaTypes, cropPropertyAlias, cropName));

            return rVal;
        }

        /// <summary>
        /// Returns the current media item and it's children (not descendants).
        /// </summary>
        /// <param name="mediaId"></param>
        /// <param name="allowedExtensions"></param>
        /// <param name="allowedMediaTypes"></param>
        /// <returns></returns>
        public List<Dictionary<string, object>> GetAllMediaItems(int mediaId, string allowedExtensions, string allowedMediaTypes, string cropPropertyAlias, string cropName)
        {
            XDocument xmlDocument = null;

            if (mediaId == -1)
            {
                xmlDocument = new XDocument(new XElement("Media"));
            }
            else
            {
                xmlDocument = new XDocument(XElement.Parse(library.GetMedia(mediaId, false).Current.OuterXml));
            }

            //Loop through all the media items to create the xml we'll use to display media.
            using (IRecordsReader dr = SqlHelper.ExecuteReader("Select id from umbracoNode where nodeObjectType = @type And parentId = @parentId order by sortOrder",
                SqlHelper.CreateParameter("@type", Media._objectType),
                SqlHelper.CreateParameter("@parentId", mediaId)))
            {
                while (dr.Read())
                {
                    //Get the xml from each media item and add it to the xmlDocument.
                    xmlDocument.Root.Add(XElement.Parse(library.GetMedia(dr.GetInt("id"), false).Current.OuterXml));
                }
            }

            var rVal = new List<Dictionary<string, object>>();
            
            if (!allowedMediaTypes.StartsWith(",") && !string.IsNullOrEmpty(allowedMediaTypes))
            {
                allowedMediaTypes = "," + allowedMediaTypes;
            }

            if (!allowedMediaTypes.EndsWith(",") && !string.IsNullOrEmpty(allowedMediaTypes))
            {
                allowedMediaTypes = allowedMediaTypes + ",";
            }

            var allDocs = xmlDocument.CreateNavigator();
            if (allDocs != null)
            {
                //Get all media items that have the allowed media type. Only the current node and children are returned.
                var expr = !string.IsNullOrEmpty(allowedMediaTypes) ?
                    allDocs.Compile("*[@nodeTypeAlias and contains('" + allowedMediaTypes + "',concat(',',@nodeType,','))] | */*[@nodeTypeAlias and contains('" + allowedMediaTypes + "',concat(',',@nodeType,','))]")
                    : allDocs.Compile("*[@nodeTypeAlias] | */*[@nodeTypeAlias]");

                try
                {
                    var iterator = allDocs.Select(expr);
                    while (iterator.MoveNext())
                    {
                        var childDocument = XDocument.Parse(iterator.Current.OuterXml);

                        //Get the root element.
                        var mediaElement = childDocument.Root;

                        bool hasAllowedExtension = DigibizMediaHelper.HasAllowedExtension(mediaElement, allowedExtensions);

                        //Only add the media item to the dictionary if the extension is allowed.
                        if (hasAllowedExtension)
                        {
                            var dictItem = new Dictionary<string, object>();
                            dictItem.Add("mediaId", DigibizMediaHelper.GetMediaId(mediaElement));
                            dictItem.Add("mediaName", DigibizMediaHelper.GetMediaName(mediaElement));
                            dictItem.Add("mediaIcon", DigibizMediaHelper.GetMediaIcon(mediaElement));
                            dictItem.Add("mediaLink", DigibizMediaHelper.GetMediaLink(mediaElement));
                            dictItem.Add("mediaCrop", DigibizMediaHelper.GetMediaCrop(mediaElement, cropPropertyAlias, cropName));
                            dictItem.Add("mediaImageTag", 
                                DigibizMediaHelper.ShowImage(
                                    dictItem["mediaLink"].ToString(), 
                                    DigibizConstants.IMAGEPREVIEWWIDTH_SELECT, 
                                    DigibizConstants.IMAGEPREVIEWHEIGHT_SELECT, 
                                    (int)dictItem["mediaId"],
                                    crop: dictItem["mediaCrop"] != null ? dictItem["mediaCrop"].ToString() : "", 
                                    icon: dictItem["mediaIcon"].ToString(), 
                                    cssClass: "mediaItem"));
                            rVal.Add(dictItem);
                        }
                    }
                }
                catch (Exception) { }
            }

            return rVal;
        }

        /// <summary>
        /// Check if the Media Type the user wants to create is allowed under the selected parent media item.
        /// </summary>
        /// <param name="nodeId"></param>
        /// <param name="mediaTypeId"></param>
        /// <returns></returns>
        [WebMethod]
        [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
        public Dictionary<string, object> CheckCreateData(int nodeId, int mediaTypeId)
        {
            string message = string.Empty;

            if (nodeId == DigibizConstants.NOACCESSID)
            {
                message = "- You do not have permission for this media tree.";
            }
            else if (nodeId != -1)
            {
                //Get the selected parent media item (usually a folder).
                Media selectedMedia = new Media(nodeId);

                //Get the content type of the this parent media item.
                ContentType selectedMediaType = selectedMedia.ContentType;

                //Check if the media type the user wants to create is allowed under the selected parent media item.
                if (!selectedMediaType.AllowedChildContentTypeIDs.Contains(mediaTypeId))
                {
                    //Create a message to show if it's not allowed.
                    MediaType createMediaType = new MediaType(mediaTypeId);
                    message = "- The selected parent is of Media Type \"" + selectedMediaType.Text + "\" and does not allow the Media Type \"" + createMediaType.Text + "\".";
                }
            }

            //Return the message.
            Dictionary<string, object> rVal = new Dictionary<string, object>();
            rVal.Add("message", message);
            return rVal;
        }

        [WebMethod]
        [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
        public Dictionary<string, object> CheckCreateMediaType(int selectedMediaTypeId)
        {
            bool hasUploadField = false;

            if (selectedMediaTypeId != -1)
            {
                hasUploadField = DigibizMediaHelper.HasUploadDataType(selectedMediaTypeId);

                if (!hasUploadField)
                {
                    //If the media type doesn't have an upload field check all it's parents.
                    hasUploadField = CheckParentMediaType(selectedMediaTypeId);
                }
            }

            //Return
            Dictionary<string, object> rVal = new Dictionary<string, object>();
            rVal.Add("hasUploadFile", hasUploadField.ToString().ToLower());
            return rVal;
        }

        /// <summary>
        /// Check if the parent media type has an upload field.
        /// </summary>
        /// <param name="mediaTypeId"></param>
        /// <returns></returns>
        private bool CheckParentMediaType(int mediaTypeId)
        {
            string sqlParent = string.Empty;

            //Get the parent (base) media type.
            if (umbraco.GlobalSettings.VersionMajor > 4)
            {
                sqlParent = string.Format(@"
                            select parentContentTypeId from cmsContentType2ContentType
                            where childContentTypeId = {0}", mediaTypeId);
            }
            else
            {
                sqlParent = string.Format(@"
                            select masterContentType from cmsContentType cct
                            where cct.nodeId = {0}", mediaTypeId);
            }

            //Execute the query and get the parent id.
            int? parentId = SqlHelper.ExecuteScalar<int?>(sqlParent);

            //Check if a parent is found.
            if (parentId.HasValue && parentId.Value != 0)
            {
                //Check if the parent (base media type) does have a upload datatype.
                bool hasUploadField = DigibizMediaHelper.HasUploadDataType(parentId.Value);

                if (hasUploadField)
                {
                    //If an upload field is found we can exit this method.
                    return true;
                }

                //If a parent is found, but no upload datatype check that parent again recurisve.
                return CheckParentMediaType(parentId.Value);
            }

            //If no parent is found there also isn't an upload field.
            return false;
        }

        /// <summary>
        /// Returns the media item as json for displaying it in the datatype.
        /// </summary>
        /// <param name="nodeId"></param>
        /// <returns></returns>
        [WebMethod]
        [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
        public Dictionary<string, string> GetEditorData(int nodeId, string cropPropertyAlias, string cropName, int thumbWidth, int thumbHeight)
        {
            //Get the media item from the umbraco cache.
            XPathNodeIterator mediaIterator = library.GetMedia(nodeId, false);

            //Convert the XPathNodeIterator to an XDocument.
            XDocument xmlDocument = XDocument.Parse(mediaIterator.Current.OuterXml);

            //Get the root element.
            XElement mediaElement = xmlDocument.Root;

            Dictionary<string, string> rVal = new Dictionary<string, string>();
            rVal.Add("mediaName", DigibizMediaHelper.GetMediaName(mediaElement));
            rVal.Add("mediaLink", DigibizMediaHelper.GetMediaLink(mediaElement));
            rVal.Add("mediaIcon", DigibizMediaHelper.GetMediaIcon(mediaElement));
            rVal.Add("mediaType", DigibizMediaHelper.GetMediaType(mediaElement)); //TF: Exception was being caused without this in javascript
            rVal.Add("mediaCrop", DigibizMediaHelper.GetMediaCrop(mediaElement, cropPropertyAlias, cropName));
            rVal.Add("mediaImageTag", DigibizMediaHelper.ShowImage(rVal["mediaLink"], thumbWidth, thumbHeight, DigibizMediaHelper.GetMediaId(mediaElement), crop: rVal["mediaCrop"], icon: rVal["mediaIcon"]));

            return rVal;
        }

        #region Get Media Names - AutoSuggest
        /// <summary>
        /// Returns the available media names
        /// </summary>
        /// <param name="nodeId"></param>
        /// <returns></returns>
        [WebMethod]
        [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
        public string[] GetMediaNames(int nodeId, string allowedExtensions, string allowedMediaTypes, string cropPropertyAlias, string cropName)
        {
            List<string> rVal = new List<string>();

            //Get the media item from the umbraco cache.
            XPathNodeIterator mediaIterator = library.GetMedia(nodeId, false);

            //Convert the XPathNodeIterator to an XDocument.
            XDocument xmlDocument = XDocument.Parse(mediaIterator.Current.OuterXml);

            //Get the root element.
            XElement mediaElement = xmlDocument.Root;

            allowedExtensions = allowedExtensions.Replace(" ", string.Empty).ToLower();

            rVal.AddRange(GetAllMediaNames(DigibizMediaHelper.GetMediaId(mediaElement), allowedExtensions, allowedMediaTypes, cropPropertyAlias, cropName));

            return rVal.ToArray();
        }

        /// <summary>
        /// Returns a list of available media names
        /// </summary>
        /// <param name="mediaId"></param>
        /// <param name="allowedExtensions"></param>
        /// <param name="allowedMediaTypes"></param>
        /// <returns></returns>
        public List<string> GetAllMediaNames(int mediaId, string allowedExtensions, string allowedMediaTypes, string cropPropertyAlias, string cropName)
        {
            XDocument xmlDocument = null;

            if (mediaId == -1)
            {
                xmlDocument = new XDocument(new XElement("Media"));
            }
            else
            {
                xmlDocument = new XDocument(XElement.Parse(library.GetMedia(mediaId, false).Current.OuterXml));
            }

            //Loop through all the media items to create the xml we'll use to display media.
            using (IRecordsReader dr = SqlHelper.ExecuteReader("Select id from umbracoNode where nodeObjectType = @type",
                SqlHelper.CreateParameter("@type", Media._objectType)))
            {
                while (dr.Read())
                {

                    //Get the xml from each media item and add it to the xmlDocument.
                    xmlDocument.Root.Add(XElement.Parse(library.GetMedia(dr.GetInt("id"), false).Current.OuterXml));
                }
            }

            var rVal = new List<string>();

            if (!allowedMediaTypes.StartsWith(",") && !string.IsNullOrEmpty(allowedMediaTypes))
            {
                allowedMediaTypes = "," + allowedMediaTypes;
            }

            if (!allowedMediaTypes.EndsWith(",") && !string.IsNullOrEmpty(allowedMediaTypes))
            {
                allowedMediaTypes = allowedMediaTypes + ",";
            }

            var allDocs = xmlDocument.CreateNavigator();
            if (allDocs != null)
            {
                //Get all media items that have the allowed media type. Only the current node and children are returned.
                var expr = !string.IsNullOrEmpty(allowedMediaTypes) ?
                    allDocs.Compile("*[@nodeTypeAlias and contains('" + allowedMediaTypes + "',concat(',',@nodeType,',')) and contains(concat(',',@path, ','),'," + mediaId.ToString() + "')] | */*[@nodeTypeAlias and contains('" + allowedMediaTypes + "',concat(',',@nodeType,',')) and contains(concat(',',@path, ','),'," + mediaId.ToString() + "')]")
                    : allDocs.Compile("*[@nodeTypeAlias and contains(concat(',',@path, ','),'," + mediaId.ToString() + "')] | */*[@nodeTypeAlias and contains(concat(',',@path, ','),'," + mediaId.ToString() + "')]");

                try
                {
                    var iterator = allDocs.Select(expr);
                    while (iterator.MoveNext())
                    {
                        var childDocument = XDocument.Parse(iterator.Current.OuterXml);

                        //Get the root element.
                        var mediaElement = childDocument.Root;

                        bool hasAllowedExtension = DigibizMediaHelper.HasAllowedExtension(mediaElement, allowedExtensions);

                        //Only add the media item to the dictionary if the extension is allowed.
                        if (hasAllowedExtension)
                        {
                            rVal.Add(DigibizMediaHelper.GetMediaName(mediaElement));
                        }
                    }
                }
                catch (Exception) { }
            }

            return rVal;
        }

        #endregion

        #region Search Related Web Methods
        /// <summary>
        /// Returns the media item as json for selecting in the popup.
        /// </summary>
        /// <param name="nodeId"></param>
        /// <returns></returns>
        [WebMethod]
        [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
        public Dictionary<string, object> GetSearchData(int nodeId, string searchTerm, string allowedExtensions, string allowedMediaTypes, string cropPropertyAlias, string cropName)
        {
            Dictionary<string, object> rVal = new Dictionary<string, object>();

            //Get the media item from the umbraco cache.
            XPathNodeIterator mediaIterator = library.GetMedia(nodeId, false);

            //Convert the XPathNodeIterator to an XDocument.
            XDocument xmlDocument = XDocument.Parse(mediaIterator.Current.OuterXml);

            //Get the root element.
            XElement mediaElement = xmlDocument.Root;

            allowedExtensions = allowedExtensions.Replace(" ", string.Empty).ToLower();

            rVal.Add("mediaItems", GetAllMediaBySearchTerm(searchTerm, DigibizMediaHelper.GetMediaId(mediaElement), allowedExtensions, allowedMediaTypes, cropPropertyAlias, cropName));

            return rVal;
        }

        /// <summary>
        /// Returns the media items that are allowed and have a name matching the search term
        /// </summary>
        /// <param name="mediaId"></param>
        /// <param name="allowedExtensions"></param>
        /// <param name="allowedMediaTypes"></param>
        /// <returns></returns>
        public List<Dictionary<string, object>> GetAllMediaBySearchTerm(string searchTerm, int mediaId, string allowedExtensions, string allowedMediaTypes, string cropPropertyAlias, string cropName)
        {
            XDocument xmlDocument = new XDocument(new XElement("Media"));

            //Loop through all the media items to create the xml we'll use to display media.
            using (IRecordsReader dr = SqlHelper.ExecuteReader("Select id from umbracoNode where nodeObjectType = @type And text LIKE @searchTerm order by sortOrder",
                SqlHelper.CreateParameter("@type", Media._objectType),
                SqlHelper.CreateParameter("@parentId", "-1"),
                SqlHelper.CreateParameter("@searchTerm", "%" + searchTerm + "%")))
            {
                while (dr.Read())
                {

                    //Get the xml from each media item and add it to the xmlDocument.
                    xmlDocument.Root.Add(XElement.Parse(library.GetMedia(dr.GetInt("id"), false).Current.OuterXml));
                }
            }

            var rVal = new List<Dictionary<string, object>>();

            if (!allowedMediaTypes.StartsWith(",") && !string.IsNullOrEmpty(allowedMediaTypes))
            {
                allowedMediaTypes = "," + allowedMediaTypes;
            }

            if (!allowedMediaTypes.EndsWith(",") && !string.IsNullOrEmpty(allowedMediaTypes))
            {
                allowedMediaTypes = allowedMediaTypes + ",";
            }

            var allDocs = xmlDocument.CreateNavigator();
            if (allDocs != null)
            {
                //Get all media items that have the allowed media type. Only the current node and children are returned.
                var expr = !string.IsNullOrEmpty(allowedMediaTypes) ?
                    allDocs.Compile("*[@nodeTypeAlias and contains('" + allowedMediaTypes + "',concat(',',@nodeType,',')) and contains(concat(',',@path, ','),'," + mediaId.ToString() + "')] | */*[@nodeTypeAlias and contains('" + allowedMediaTypes + "',concat(',',@nodeType,',')) and contains(concat(',',@path, ','),'," + mediaId.ToString() + "')]")
                    : allDocs.Compile("*[@nodeTypeAlias and contains(concat(',',@path, ','),'," + mediaId.ToString() + "')] | */*[@nodeTypeAlias and contains(concat(',',@path, ','),'," + mediaId.ToString() + "')]");

                try
                {
                    var iterator = allDocs.Select(expr);
                    while (iterator.MoveNext())
                    {
                        var childDocument = XDocument.Parse(iterator.Current.OuterXml);

                        //Get the root element.
                        var mediaElement = childDocument.Root;

                        bool hasAllowedExtension = DigibizMediaHelper.HasAllowedExtension(mediaElement, allowedExtensions);

                        //Only add the media item to the dictionary if the extension is allowed.
                        if (hasAllowedExtension)
                        {
                            var dictItem = new Dictionary<string, object>();
                            dictItem.Add("mediaId", DigibizMediaHelper.GetMediaId(mediaElement));
                            dictItem.Add("mediaName", DigibizMediaHelper.GetMediaName(mediaElement));
                            dictItem.Add("mediaIcon", DigibizMediaHelper.GetMediaIcon(mediaElement));
                            dictItem.Add("mediaLink", DigibizMediaHelper.GetMediaLink(mediaElement));
                            dictItem.Add("mediaCrop", DigibizMediaHelper.GetMediaCrop(mediaElement, cropPropertyAlias, cropName));
                            dictItem.Add("path", mediaElement.Attribute("path").Value);
                            dictItem.Add("mediaImageTag",
                                DigibizMediaHelper.ShowImage(
                                    dictItem["mediaLink"].ToString(),
                                    DigibizConstants.IMAGEPREVIEWWIDTH_SELECT,
                                    DigibizConstants.IMAGEPREVIEWHEIGHT_SELECT,
                                    (int)dictItem["mediaId"],
                                    crop: dictItem["mediaCrop"] != null ? dictItem["mediaCrop"].ToString() : "",
                                    icon: dictItem["mediaIcon"].ToString(),
                                    cssClass: "mediaItem"));
                            rVal.Add(dictItem);
                        }
                    }
                }
                catch (Exception) { }
            }

            return rVal;
        }
        #endregion
    }
}
